#include <dos.h>
#include "mpx.h"
#include "mpx_supt.h"
#include "dispatch.h"
#include "pcb_strc.h"
#include "com_hand.h"
#include "R5.h"
#include "TRMDRIVE.H"

unsigned short ss_save;
unsigned short sp_save;
unsigned short new_ss;
unsigned short new_sp;
unsigned short mpx_ss;
unsigned short mpx_sp;

params *param_ptr;
iocb trm_iocb;
iocb com_iocb;

pcb_rec *cop; //Current Operating Process
iod_rec *tmp_iod;
pcb_queue *ready_queue;
char sys_stack[PCB_STACK_SIZE];

int dispatch_init() {
	ready_queue = NULL;
	ready_queue = get_ready_queue();
	sys_set_vec(sys_call);
	
	ss_save = NULL;
	sp_save = NULL;
	new_ss = NULL;
	new_sp = NULL;
	mpx_ss = NULL;
	mpx_sp = NULL;
	cop = NULL;
		
	return OK;
}

//Initializes device I/O Control Blocks, opens devices
void open_io() {
	com_iocb.event_flag = 0;
	com_iocb.iod_count = 0;
	com_iocb.head = NULL;
	com_iocb.tail = NULL;
	
	trm_iocb.event_flag = 0;
	trm_iocb.iod_count = 0;
	trm_iocb.head = NULL;
	trm_iocb.tail = NULL;

	trm_open(&trm_iocb.event_flag);
	com_open(&com_iocb.event_flag, 1200);
}

//Closes I/O devices, frees IODs from IOCBs
void close_io() {
	iod_rec *next, *cur;

	trm_close();
	com_close();
	
	//free each IODs in both queues
	next = trm_iocb.head;
	while ( next != NULL) {
		cur = next;
		sys_free_mem(cur);
		next = next->next;
	}
	
	next = com_iocb.head;
	while ( next != NULL) {
		cur = next;
		sys_free_mem(cur);
		next = next->next;
	}
}

//I/O Scheduler: creates new I/O device requests and enqueues them as needed
void schedule_io() {
	iod_rec *new_iod;
	iocb *dev_iocb;
	
	switch (param_ptr->device_id) {
		case COM_PORT:
			dev_iocb = &com_iocb; break;
		case TERMINAL:
			dev_iocb = &trm_iocb; break;
		default:
			printf("Invalid I/O device ID\n"); return;
	}
	
	//Initialize new I/O device request
	new_iod = (iod_rec*) sys_alloc_mem((size_t) sizeof(iod_rec));
	new_iod->pcb = cop;
	strcpy(new_iod->pcb_name, cop->name);
	
	new_iod->buf_addr = param_ptr->buf_addr;
	new_iod->buf_cnt = param_ptr->count_addr;
	new_iod->req_type = param_ptr->op_code;
	
	//Enqueue new I/O device request
	if (dev_iocb->iod_count == 0) {
		//COM PORT has no other I/O requests: Process request NOW
		dev_iocb->head = new_iod;
		dev_iocb->tail = new_iod;
		dev_iocb->iod_count = 1;
		
		process_io_req(dev_iocb, param_ptr->device_id);
	}
	else {
		//Stuff already in device queue, enqueue request
		dev_iocb->tail->next = new_iod;
		dev_iocb->tail = new_iod;
	}
}

//Helper function, Performs actual I/O device calls
void process_io_req(iocb *dev_iocb, int device_id) {
	switch(dev_iocb->head->req_type) {
		case READ:
			if (device_id == COM_PORT) com_read(dev_iocb->head->buf_addr, dev_iocb->head->buf_cnt);
			if (device_id == TERMINAL) trm_read(dev_iocb->head->buf_addr, dev_iocb->head->buf_cnt);
			break;
		case WRITE:
			if (device_id == COM_PORT) com_write(dev_iocb->head->buf_addr, dev_iocb->head->buf_cnt);
			if (device_id == TERMINAL) trm_write(dev_iocb->head->buf_addr, dev_iocb->head->buf_cnt);
			break;
		case CLEAR:
			if (device_id == TERMINAL) trm_clear();
			break;
		case GOTOXY:
			if (device_id == TERMINAL) trm_gotoxy(7, 7); //How the hell do you get the x,y pos here?
			break;
		default:
			//printf("Invalid device request type: %d\n", dev_iocb->head->req_type);
			break;
	}
}

void interrupt sys_call() {
	//Switch to temporary stack to prevent overflow
	ss_save = _SS;
	sp_save = _SP;
	new_ss = FP_SEG(&sys_stack);
	new_sp = FP_OFF(&sys_stack) + PCB_STACK_SIZE;
	_SS = new_ss;
	_SP = new_sp;
	
	param_ptr = (params*)((unsigned char*)MK_FP(ss_save, sp_save) + sizeof(context));
	//Flush keyboard buffer into MPX buffer
	trm_getc();
	
	//If COM_PORT finished a request, reset event flag and do the next thing
	if (com_iocb.event_flag == 1) {
		com_iocb.event_flag = 0;
		
		tmp_iod = com_iocb.head;
		com_iocb.head = com_iocb.head->next;
		com_iocb.iod_count--;
		
		if (com_iocb.iod_count == 0) {
			com_iocb.head = NULL;
			com_iocb.tail = NULL;
		}
		
		remove_pcb(tmp_iod->pcb);
		tmp_iod->pcb->state = PROC_STATE_READY;
		insert_pcb(tmp_iod->pcb);
		
		//cmd_unblock(tmp_iod->pcb_name);
		
		sys_free_mem(tmp_iod);
		
		if(com_iocb.iod_count > 0)
			process_io_req(&com_iocb, COM_PORT);
	}

	//If TERMINAL finished a request, reset event flag and do the next thing
	if (trm_iocb.event_flag == 1) {
		trm_iocb.event_flag = 0;
		
		tmp_iod = trm_iocb.head;
		trm_iocb.head = trm_iocb.head->next;
		trm_iocb.iod_count--;
		
		if (trm_iocb.iod_count == 0) {
			trm_iocb.head = NULL;
			trm_iocb.tail = NULL;
		}
			
		remove_pcb(tmp_iod->pcb);
		tmp_iod->pcb->state = PROC_STATE_READY;
		insert_pcb(tmp_iod->pcb);
		
		sys_free_mem(tmp_iod);
		
		if(trm_iocb.iod_count > 0)
			process_io_req(&trm_iocb, TERMINAL);
	}

	
	switch (param_ptr->op_code) {
		case IDLE:
			cop->state = PROC_STATE_READY;
			cop->stack_top = MK_FP(ss_save, sp_save);
			insert_pcb(cop);
			break;
		case EXIT:
			free_pcb(cop);
			cop = NULL;
			break;
		case READ:
			schedule_io();
			cop->state = PROC_STATE_BLOCKED;
			cop->stack_top = MK_FP(ss_save, sp_save);
			insert_pcb(cop);
			break;
		case WRITE:
			schedule_io();
			cop->state = PROC_STATE_BLOCKED;
			cop->stack_top = MK_FP(ss_save, sp_save);
			insert_pcb(cop);
			break;
		case CLEAR:
			schedule_io();
			cop->state = PROC_STATE_BLOCKED;
			cop->stack_top = MK_FP(ss_save, sp_save);
			insert_pcb(cop);
			break;
		case GOTOXY:
			schedule_io();
			cop->state = PROC_STATE_BLOCKED;
			cop->stack_top = MK_FP(ss_save, sp_save);
			insert_pcb(cop);
			break;
		default:
			break;
	}
	
	_SS = ss_save;
	_SP = sp_save;
	
	//Flush keyboard buffer into MPX buffer
	dispatch();
}

void interrupt dispatch() {
	if (mpx_sp == NULL) {
		mpx_ss = _SS;
		mpx_sp = _SP;
	}
	
	//PCB exists in Ready Queue
	if (ready_queue->front != NULL) {
		cop = ready_queue->front->pcb;
		remove_pcb(cop);
		cop->state = PROC_STATE_RUNNING;
		
		new_ss = FP_SEG(cop->stack_top);
		new_sp = FP_OFF(cop->stack_top);
		_SS = new_ss;
		_SP = new_sp;
		
	}
	//Ready queue is empty, return control to MPX
	else {
		cop = NULL;
		_SS = mpx_ss;
		_SP = mpx_sp;
		
		ss_save = NULL;
		sp_save = NULL;
	}
}

void load_process(char *filename, int priority, int class) {
	int prog_length, start_offset, ret_code;
	pcb_rec *pcb;
	context *cp;

	ret_code = sys_check_program("\0", filename, &prog_length, &start_offset);
	if (ret_code != 0) {
		printf("ERROR: Something went wrong checking %s.mpx (Error Code %d)\n",filename,ret_code);
		return;
	}
	
	pcb = setup_pcb(filename, priority, class);
	pcb->state = PROC_STATE_SUSPENDED_READY;
	
	pcb->mem_size  = prog_length;
	pcb->load_addr = (unsigned char*)sys_alloc_mem(prog_length);
	pcb->exec_addr = pcb->load_addr + start_offset;
	
	cp = (context*) pcb->stack_top;
	cp->IP = FP_OFF(pcb->exec_addr);
	cp->CS = FP_SEG(pcb->exec_addr);
	cp->DS = _DS;
	cp->ES = _ES;
	cp->FLAGS = 0x200;
	
	ret_code = sys_load_program(pcb->load_addr, pcb->mem_size, "\0", filename);
	if (ret_code != 0) {
		printf("ERROR: Something went wrong loading %s.mpx (Error Code %d)\n",filename,ret_code);
		return;
	}
	insert_pcb(pcb);
//	printf("Process %s.mpx loaded.\n\n**** PRESS PLAY ON TAPE ****\n(resume each process, then type run)\n",filename);
}

int load_comhan_proc() {
	pcb_rec *pcb;
	context *proc_context;
	
	if (find_pcb("comhan")) {
		printf("Comhan process already exists\n");
		return ERROR;
	}
	
	pcb = setup_pcb("comhan", 127, PROC_CLASS_SYS);
	pcb->state = PROC_STATE_READY;
	proc_context = (context*)pcb->stack_top;
	
	proc_context->IP = FP_OFF(&com_handler_repl);
	proc_context->CS = FP_SEG(&com_handler_repl);
	proc_context->FLAGS = 0x200;
	proc_context->DS = _DS;
	proc_context->ES = _ES;
	
	insert_pcb(pcb);
}

int load_processes() {
	pcb_rec *pcb;
	context *proc_context;
	
	if (find_pcb("test1")) {
		printf("Test Process 1 already loaded\n");
	}
	else {
		pcb = setup_pcb("test1", 0, PROC_CLASS_APP);
		proc_context = (context*)pcb->stack_top;
	
		proc_context->IP = FP_OFF(&test1_R3);
		proc_context->CS = FP_SEG(&test1_R3);
		proc_context->FLAGS = 0x200;
		proc_context->DS = _DS;
		proc_context->ES = _ES;
	
		insert_pcb(pcb);
	}
	
	if (find_pcb("test2")) {
		printf("Test Process 2 already loaded\n");
	}
	else {
		pcb = setup_pcb("test2", 0, PROC_CLASS_APP);
		proc_context = (context*)pcb->stack_top;
	
		proc_context->IP = FP_OFF(&test2_R3);
		proc_context->CS = FP_SEG(&test2_R3);
		proc_context->FLAGS = 0x200;
		proc_context->DS = _DS;
		proc_context->ES = _ES;
	
		insert_pcb(pcb);
	}
	
	if (find_pcb("test3")) {
		printf("Test Process 3 already loaded\n");
	}
	else {
		pcb = setup_pcb("test3", 0, PROC_CLASS_APP);
		proc_context = (context*)pcb->stack_top;
		
		proc_context->IP = FP_OFF(&test3_R3);
		proc_context->CS = FP_SEG(&test3_R3);
		proc_context->FLAGS = 0x200;
		proc_context->DS = _DS;
		proc_context->ES = _ES;
		
		insert_pcb(pcb);
	}
	
	if (find_pcb("test4")) {
		printf("Test Process 4 already loaded\n");
	}
	else {
		pcb = setup_pcb("test4", 0, PROC_CLASS_APP);
		proc_context = (context*)pcb->stack_top;
		
		proc_context->IP = FP_OFF(&test4_R3);
		proc_context->CS = FP_SEG(&test4_R3);
		proc_context->FLAGS = 0x200;
		proc_context->DS = _DS;
		proc_context->ES = _ES;
		
		insert_pcb(pcb);
	}
	
	if (find_pcb("test5")) {
		printf("Test Process 5 already loaded\n");
	}
	else {
		pcb = setup_pcb("test5", 0, PROC_CLASS_APP);
		proc_context = (context*)pcb->stack_top;
		
		proc_context->IP = FP_OFF(&test5_R3);
		proc_context->CS = FP_SEG(&test5_R3);
		proc_context->FLAGS = 0x200;
		proc_context->DS = _DS;
		proc_context->ES = _ES;
		
		insert_pcb(pcb);
	}
	
	return OK;
}